package org.springextensions.actionscript.ioc.factory.xml.preprocessors
{
	import org.as3commons.reflect.ClassUtils;
	import org.springextensions.actionscript.ioc.MasterResourceGroup;
	import org.springextensions.actionscript.ioc.ResourceGroup;
	import org.springextensions.actionscript.ioc.factory.xml.parser.IXMLObjectDefinitionsPreprocessor;
	import org.springextensions.actionscript.ioc.util.ResourceConstants;

	public class RequiredResourcesPreprocessor implements IXMLObjectDefinitionsPreprocessor
	{
		private var _buckets:Array = [];
		
		private var _resolved:Array = [];
		
		public function preprocess(xml:XML):XML
		{
			_prepareBuckets(xml);
			
			if (_buckets.length > 0)
			{
				_addResourceGroups(xml);
			}
			
			for (var i:int = 0; i < _buckets.length; i++)
			{
				trace("bucket " + i + " => " + _buckets[i]);
			}
			
			return xml;
		}
		
		private function _addResourceGroups(xml:XML):void 
		{
			var masterResourceGroupXML:XML = _resourceGroupXML(ResourceConstants.MASTER_RESOURCE_GROUP_ID, ClassUtils.getFullyQualifiedName(MasterResourceGroup, true));
			
			for (var i:int = 0; i < _buckets.length; i++)
			{
				var resources:Array = _buckets[i] as Array;
				
				var id:String = ResourceConstants.RESOURCE_GROUP_NAME.split(ResourceConstants.INDEX_TOKEN).join(i);
				
				var resourceGroupXML:XML = _resourceGroupXML(id, ClassUtils.getFullyQualifiedName(ResourceGroup, true));
                
                // add dependant resources
		        var resourcesNode:XML = resourceGroupXML.children()[0];
		        
		        for each (var resource:String in resources) {
		          resourcesNode.children()[0].appendChild(<ref>{resource}</ref>);
		        }
		        
		        // add the newly created node
        		xml.appendChild(resourceGroupXML);
        		
        		// add this resource group's id to the master resource group
        		masterResourceGroupXML.children()[0].children()[0].appendChild(<value>{id}</value>);
			}
			
			// add the master resource group node
			xml.appendChild(masterResourceGroupXML);
		}
		
		private function _resourceGroupXML(id:String, className:String):XML 
		{
			return <object id={id} class={className} lazy-init="true">
                            <property name="resources">
                              <array/>
                            </property>
                          </object>;
		}
		
		private function _prepareBuckets(xml:XML):void 
		{
			var nodes:XMLList = xml.resource;
			
			for (var i:int = 0; i < nodes.length(); i++)
			{
				var node:XML = nodes[i];
				
				_iterateOverResourceNodeWithName(xml, node.@id, 0);
			}
		}
		
		/**
		 * This is a recursive method that arranges required resources into levels
		 * so that they can all be loaded in the correct order - so no resource classes
		 * are instantiated until all their dependencies are completely loaded.
		 * 
		 * For instance, if an application context contains the following resource dependencies:
		 * 
		 * Resource A has no dependencies.
		 * Resource B has no dependencies.
		 * Resource C depends on Resource B.
		 * Resource D depends on Resource C.
		 * 
		 * This method will create a 2-dimensional array with the following structure:
		 * 
		 * [[Resource A, Resource B],
		 *  [Resource C],
		 *  [Resource D]]
		 * 
		 * So loading these resources in order will prevent runtime errors.
		 */
		private function _iterateOverResourceNodeWithName(xml:XML, nodeName:String, index:int):int
		{			
			// Check if we've already put this node in a bucket.
			if (!_isResolved(nodeName))
			{				
				var node:XML = xml.resource.(@id == nodeName)[0];

				// Check to see if this resource has dependencies.
				if (_hasDependancies(node))
				{		
					var dependancies:XMLList = _getDependencies(node);
					
					// If we find dependencies we have to iterate over them.
					// We also need to increment the index for this resource for
					// each level of dependency we find.
					for each (var dependancy:XML in dependancies)
					{
						var i:int = _iterateOverResourceNodeWithName(xml, dependancy.valueOf(), index);
						
						if (i >= index)
						{
							index = i + 1;
						}
					}
				}

				// Put this node name in a bucket and register the name 
				// so we don't try and resolve it again.
				_getBucketAtIndex(index).push(nodeName);
				_resolved.push(nodeName);
			}
			else
			{
				// If we've already resolved this resource name 
				// then return the index for this resource
				// so the resource that depends on it is at 
				// least one level higher.
				return _getBucketIndexForName(nodeName);
			}
			
			// Return the index we've got to for this resource name.
			return index;
		}
		
		private function _getBucketIndexForName(name:String):int 
		{
			for (var i:int = 0; i < _buckets.length; i++)
			{
				for each (var val:String in _buckets[i])
				{
					if (val == name)
					{
						return i;
					}
				}
			}
			
			return i;
		}
		
		private function _getBucketAtIndex(index:int):Array
		{
			if (_buckets[index] == null)
			{
				_buckets[index] = new Array();
			}
			
			return _buckets[index] as Array;
		}
		
		private function _getDependencies(node:XML):XMLList
		{
			return node.property.(@name == "depends")[0].children()[0].children();
		}
		
		private function _hasDependancies(node:XML):Boolean 
		{
			return node.property.(@name == "depends").length() > 0;
		}
		
		private function _isResolved(resourceName:String):Boolean
		{
			for each (var name:String in _resolved)
			{
				if (name == resourceName)
				{
					return true;
				}
			}
			
			return false;
		}
	}
}